package com.awesumgames.awesum;

import static java.lang.Math.PI;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.sqrt;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import tv.ouya.console.api.OuyaController;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.util.Log;
import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import com.awesumgames.awesum.Vector3;
import com.awesumgames.awesum.loaders.collada.Collada;

public final class AwesumGlobal {
	public static final class Awesum {
		/* PACKAGE LEVEL STUFF -------------------------------------------*/
		public static Context context = null;

		public final static FloatBuffer makeFloatBuffer(float[] arr) {
			FloatBuffer fb = ByteBuffer.allocateDirect(arr.length * Game.FloatSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
			fb.put(arr).position(0);
			return fb;
		}
		public final static FloatBuffer makeFloatBuffer(Float[] arr) {
			FloatBuffer fb = ByteBuffer.allocateDirect(arr.length * Game.FloatSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
			
			//convert Float[] to float[]
			float[] fa = new float[arr.length];
			int i = 0;
			for (Float f : arr)
				fa[i++] = f != null ? f : 0f; 
			
			fb.put(fa).position(0);
			return fb;
		}
		public final static ByteBuffer makeByteBuffer(byte[] arr) {
			ByteBuffer bb = ByteBuffer.allocateDirect(arr.length).order(ByteOrder.nativeOrder());
			bb.put(arr).position(0);
			return bb;
		}
		public final static ByteBuffer makeByteBuffer(Byte[] arr) {
			ByteBuffer bb = ByteBuffer.allocateDirect(arr.length).order(ByteOrder.nativeOrder());

			//convert Float[] to float[]
			byte[] ba = new byte[arr.length];
			int i = 0;
			for (Byte b : arr)
				ba[i++] = b != null ? b : 0; 
			
			bb.put(ba).position(0);
			return bb;
		}
		public final static ShortBuffer makeShortBuffer(short[] arr) {
			ShortBuffer sb = ByteBuffer.allocateDirect(arr.length * Game.ShortSize).order(ByteOrder.nativeOrder()).asShortBuffer();
			sb.put(arr).position(0);
			return sb;
		}
		public final static ShortBuffer makeShortBuffer(Short[] arr) {
			ShortBuffer sb = ByteBuffer.allocateDirect(arr.length * Game.ShortSize).order(ByteOrder.nativeOrder()).asShortBuffer();
			
			short[] sa = new short[arr.length];
			int i = 0;
			for (Short s : arr)
				sa[i++] = s != null ? s : 0;
			
			sb.put(sa).position(0);
			return sb;
		}
		
		/* PUBLIC STUFF --------------------------------------------------*/
		public static final float degToRad = (float)(PI / 180f);
		public static final float radToDeg = (float)(180f / PI);
		
		public final static float angDiff(float aFrom, float aTo) {
			float ang = aFrom - aTo;
			if (Math.abs(ang / (float)Math.PI) > 1f)
				ang = -ang % (float)Math.PI;
			return ang;
		}
	
		public final static float clamp(float x, float min, float max) {
			return max(min(x, max), min);
		}
		public final static float distance(Vector3 v1, Vector3 v2) {
			return (float)sqrt(distanceSq(v1, v2));
		}
		public final static float distanceSq(Vector3 v1, Vector3 v2) {
	        //Squared distance between two Vector3s
			//This is preferable if possible, to avoid the square root
			float	dx = v2.x - v1.x,
					dy = v2.y - v1.y,
					dz = v2.z - v1.z;
			return dx*dx + dy*dy + dz*dz;
		}
		
		public final static void debug(String msg) {
			Log.d("debug", msg);
		}
		public final static void debug(String txt, FloatBuffer var) {
			String msg = txt;
			for (int i = 0; i < var.capacity(); i++)
				msg += " " + var.get(i); 
			debug(msg);
		}
		public final static void debug(String txt, ShortBuffer var) {
			String msg = txt;
			for (int i = 0; i < var.capacity(); i++)
				msg += ", " + var.get(i);
			debug(msg);
		}
		
		public final static void setup(Context c) {
			context = c;
		}
		
		public static final class Load {
			public static final JSONObject Json(int file) throws JSONException, IOException {
				/*InputStream is = Awesum.context().getResources().openRawResource(file);
			    JsonReader reader = new JsonReader(new InputStreamReader(is, "UTF-8"));
			    try {return reader;} finally {reader.close();}*/
				return (JSONObject)new JSONTokener(File(file)).nextValue();
			}
			
			public static final String File(int file) throws IOException {
				InputStream is = Awesum.context.getResources().openRawResource(file);
				Writer writer = new StringWriter();
				char[] buffer = new char[1024];
				try {
				    Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
				    int n;
				    while ((n = reader.read(buffer)) != -1)
				        writer.write(buffer, 0, n);
				} finally {
				    is.close();
				}

				return writer.toString();
			}
			
			public static int loadTexture(final Context context, final int resourceId) {
			    final int[] textureHandle = new int[1];
			    GLES20.glGenTextures(1, textureHandle, 0);
			    if (textureHandle[0] != 0) {
			        final BitmapFactory.Options options = new BitmapFactory.Options();
			        options.inScaled = false;   // No pre-scaling
			        final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
			        // Bind to the texture in OpenGL
			        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
			        // Set filtering
			        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
			        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
			        // Load the bitmap into the bound texture.
			        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
			        // Recycle the bitmap, since its data has been loaded into OpenGL.
			        bitmap.recycle();
			    }
			    if (textureHandle[0] == 0)
			        throw new RuntimeException("Error loading texture.");
			    return textureHandle[0];
			}
			
			private class Triangle {
				public Vector3 v1, v2, v3, norm;
				Triangle(Vector3 vec1, Vector3 vec2, Vector3 vec3) {
					v1 = vec1; v2 = vec2; v3 = vec3;
					
					Vector3 p1 = v3.copy().negate2().add(v2);
					Vector3 p2 = v3.negate2().add(v1); //don't need to copy() v3 here
					norm = p1.cross(p2);
				}
				Triangle(Vector3 vec1, Vector3 vec2, Vector3 vec3, Vector3 normal) {
					v1 = vec1; v2 = vec2; v3 = vec3;
					
					norm = normal;
				}
			}
			
			private static void buildArrays(Drawable d, ArrayList<Triangle> model) {
				
				//d.gl.setVertices(vbo[0]);
				//d.gl.setIndices(vbo[1]);
			}
			
			public static final void StlText(Drawable d, int file) throws IOException {
				ArrayList<Triangle> model = new ArrayList<Triangle>();
				ArrayList<Vector3> triangle = new ArrayList<Vector3>();
				String modelData = File(file), line = null, name = null;
				String[] lines = modelData.split("\r?\n|\r"), tokens = null;
				Vector3 normal = null;
				
				for (int i = 0; i < lines.length; ++i) {
					line = lines[i];
					tokens = line.split("\\s+");
					if (tokens.length > 0) {
						if (tokens[0].equals("solid")) {
							name = tokens[1];
						} else if (tokens[0].equals("facet")) {
							triangle.clear();
							normal = new Vector3(Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3]), Float.parseFloat(tokens[4]));
						} else if (tokens[0].equals("vertex")) {
							triangle.add(new Vector3(Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3])));
						} else if (tokens[0].equals("endloop")) {
							if (triangle.size() == 3) {
								model.add(new Load().new Triangle(triangle.get(0), triangle.get(1),triangle.get(2)));
								normal = null;
							} //TODO: else exception: broken STL file 
						}
					}
				}
				//turn model list into a dang float array!
				buildArrays(d, model);
				d.gl.setDrawModeTriangles();
			}
			
			public static final void StlBinary(int file) throws IOException {
				ArrayList<Triangle> model = new ArrayList<Triangle>();
				ArrayList<Vector3> triangle = new ArrayList<Vector3>();
				String modelData = File(file), line = null, name = null;
				String[] lines = modelData.split("\r?\n|\r"), tokens = null;
				Vector3 normal = null;
				
				for (int i = 0; i < lines.length; ++i) {
					line = lines[i];
					tokens = line.split("\\s+");
					if (tokens.length > 0) {
						if (tokens[0].equals("solid")) {
							name = tokens[1];
						} else if (tokens[0].equals("facet")) {
							triangle.clear();
							normal = new Vector3(Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3]), Float.parseFloat(tokens[4]));
						} else if (tokens[0].equals("vertex")) {
							triangle.add(new Vector3(Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3])));
						} else if (tokens[0].equals("endloop")) {
							if (triangle.size() == 3) {
								model.add(new Load().new Triangle(triangle.get(0), triangle.get(1),triangle.get(2)));
								normal = null;
							} //TODO: else exception: broken STL file 
						}
					}
				}
				//turn model list into a dang float array!
			}
			
			public static final void Dae(Drawable d, int file) {
				Collada c = Awesum.Load.Obj(file);

				d.gl.model.vertices = Awesum.makeFloatBuffer(c.vertices);
				d.gl.model.normals = Awesum.makeFloatBuffer(c.normals);
				d.gl.model.indices = Awesum.makeShortBuffer(c.indices);

				d.gl.setDrawModeTriangles();
				d.gl.load();
			}
			
			private static final Collada Obj(int file) {
				try {
					XmlPullParser xpp = Xml.newPullParser();
					xpp.setInput(new StringReader(File(file)));
					//xpp.require(XmlPullParser.START_TAG, null, "library_geometries");
					
					Collada c = new Collada();
					float vertices[] = null;
					float normals[] = null;
					short indices[] = null;
					
					int eventType = xpp.getEventType();
					while (eventType != XmlPullParser.END_DOCUMENT) {
						if(eventType == XmlPullParser.START_TAG) {
							String name = xpp.getName();
							if (name.equals("source")) {
								xpp.nextTag();
								if (xpp.getName().equals("float_array")) {
									String id = xpp.getAttributeValue(null, "id");
									int count = Integer.parseInt(xpp.getAttributeValue(null, "count"));
									
									if (id.endsWith("positions-array")) {
										vertices = new float[count];
										xpp.next();
										String[] values = xpp.getText().split("\\s+");
										for (int i = 0; i < values.length; ++i)
											vertices[i] = Float.parseFloat(values[i]);
									} else if (id.endsWith("normals-array")) {
										normals = new float[count];
										xpp.next();
										String[] values = xpp.getText().split("\\s+");
										for (int i = 0; i < values.length; ++i)
											normals[i] = Float.parseFloat(values[i]);
									}
								}
							} else if (name.equals("polylist")) {
								int count = Integer.parseInt(xpp.getAttributeValue(null, "count"));
								while (xpp.getEventType() != XmlPullParser.START_TAG || !xpp.getName().equals("p")) {
									xpp.next();
									if (xpp.getEventType() != XmlPullParser.START_TAG)
										continue;
								}
								xpp.next();
								String[] values = xpp.getText().split("\\s+");
								indices = new short[values.length];
								for (int i = 0; i < values.length; ++i)
									indices[i] = Short.parseShort(values[i]);
							}
						} else if(eventType == XmlPullParser.END_TAG) {
							//System.out.println("End tag "+xpp.getName());
						} else if(eventType == XmlPullParser.TEXT) {
							//System.out.println("Text "+xpp.getText());
						}
						eventType = xpp.next();
					}

					c.vertices = new float[indices.length / 2 * 3];
					c.normals = new float[indices.length / 2 * 3];
					c.indices = new short[indices.length / 2];
					for (short i = 0; i < indices.length/2; i++) {
						c.vertices[3*i] = vertices[3*indices[i*2]];
						c.vertices[3*i+1] = vertices[3*indices[i*2]+1];
						c.vertices[3*i+2] = vertices[3*indices[i*2]+2];
						
						c.normals[3*i] = normals[3*indices[i*2+1]];
						c.normals[3*i+1] = normals[3*indices[i*2+1]+1];
						c.normals[3*i+2] = normals[3*indices[i*2+1]+2];
					}
					for (short i = 0; i < c.indices.length; i++)
						c.indices[i] = i;
					
					return c;
				} catch (XmlPullParserException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
				return null;
			}
		}
		public static final class Axis {
			public static final Vector3 X = new Vector3(1f, 0f, 0f);
			public static final Vector3 Y = new Vector3(0f, 1f, 0f);
			public static final Vector3 Z = new Vector3(0f, 0f, 1f);
			public static final Vector3 combine(Vector3 v1, Vector3 v2) {
				return v1.copy().add(v2).normalize2();
			}
		}
	}
}
